package jmine.tec.web.servlet.rest;

import java.io.IOException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jmine.tec.web.servlet.rest.exception.HttpErrorException;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Classe basica para servicos REST. Cria metodos delegates com diferentes parametros para subclasses sobrescreverem apenas o necessario.
 * Caso um metodo nao seja implementado, lanca uma {@link HttpErrorException} com METHOD_NOT_ALLOWED
 * 
 * @author takeshi
 */
public abstract class AbstractRestSupport {

    /**
     * delega para {@link #get(String, Map)}
     * 
     * @param identification String
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object get(String identification, HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.get(identification, this.parameterMap(request));
    }

    /**
     * Por default, lanca o {@link HttpServletResponse} com METHOS_NOT_ALLOWED
     * 
     * @param identification String
     * @param map Map
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object get(String identification, Map<String, String[]> map) throws BeanNotFoundException, HttpErrorException {
        throw new HttpErrorException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, this.getPreffix());
    }

    /**
     * delega para {@link #get(Map)}
     * 
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object get(HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.get(this.parameterMap(request));
    }

    /**
     * Delega para {@link #get(String, Map)} passando 'null' como identification
     * 
     * @param params Map
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object get(Map<String, String[]> params) throws BeanNotFoundException, HttpErrorException {
        return this.get(null, params);
    }

    /**
     * Apenas para fazer o cast seguro do mapa de parametros do {@link HttpServletRequest}
     * 
     * @param request {@link HttpServletRequest}
     * @return {@link Map} de {@link String} para array de {@link String} representando os parametros da query string
     */
    @SuppressWarnings("unchecked")
    protected final Map<String, String[]> parameterMap(HttpServletRequest request) {
        return request.getParameterMap();
    }

    /**
     * delega para {@link #post(String, Map)}
     * 
     * @param identification String
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object post(String identification, HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.post(identification, this.parameterMap(request));
    }

    /**
     * Por default, lanca o {@link HttpServletResponse} com METHOS_NOT_ALLOWED
     * 
     * @param identification String
     * @param params Map
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object post(String identification, Map<String, String[]> params) throws BeanNotFoundException, HttpErrorException {
        throw new HttpErrorException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, this.getPreffix());
    }

    /**
     * delega para {@link #post(Map)}
     * 
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object post(HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.post(this.parameterMap(request));
    }

    /**
     * Chama {@link #post(String, Map)} passando <code>null</code> como identification
     * 
     * @param params {@link Map}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object post(Map<String, String[]> params) throws BeanNotFoundException, HttpErrorException {
        return this.post(null, params);
    }

    /**
     * delega para {@link #put(String, Map)}
     * 
     * @param identification String
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object put(String identification, HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.put(identification, this.parameterMap(request));
    }

    /**
     * Por default, chama {@link #post(String, Map)}
     * 
     * @param identification {@link String}
     * @param parameters {@link Map}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object put(String identification, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        return this.post(identification, parameters);
    }

    /**
     * Chama {@link #put(Map)}
     * 
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object put(HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.put(this.parameterMap(request));
    }

    /**
     * Chama {@link #put(String, Map)} passando <code>null</code> como identification
     * 
     * @param params {@link Map}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object put(Map<String, String[]> params) throws BeanNotFoundException, HttpErrorException {
        return this.put(null, params);
    }

    /**
     * delega para {@link #delete(String, Map)}
     * 
     * @param identification String
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object delete(String identification, HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.delete(identification, this.parameterMap(request));
    }

    /**
     * delega para {@link #delete(Map)}
     * 
     * @param request {@link HttpServletRequest}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object delete(HttpServletRequest request) throws BeanNotFoundException, HttpErrorException {
        return this.delete(this.parameterMap(request));
    }

    /**
     * Por default, lanca o {@link HttpServletResponse} com METHOS_NOT_ALLOWED
     * 
     * @param identification String
     * @param parameters Map
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object delete(String identification, Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        throw new HttpErrorException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, this.getPreffix());
    }

    /**
     * Chama {@link #delete(String, Map)} passando <code>null</code> como identification
     * 
     * @param parameters {@link Map}
     * @return Object
     * @throws BeanNotFoundException e
     * @throws HttpErrorException e
     */
    protected Object delete(Map<String, String[]> parameters) throws BeanNotFoundException, HttpErrorException {
        return this.delete(null, parameters);
    }

    /**
     * Lanca um 404
     * 
     * @param response {@link HttpServletResponse}
     * @param message String
     * @throws IOException e
     */
    protected void errorNotFound(HttpServletResponse response, String message) throws IOException {
        this.error(HttpServletResponse.SC_NOT_FOUND, message, response);
    }

    /**
     * Lanca um erro de http
     * 
     * @param httpCode o codigo de erro http
     * @param message a mensagem
     * @param response o {@link HttpServletResponse}
     * @throws IOException e
     */
    protected void error(int httpCode, String message, HttpServletResponse response) throws IOException {
        response.sendError(httpCode, "<jterror>" + message + "</jterror>");
    }

    /**
     * Lanca um 500
     * 
     * @param response {@link HttpServletResponse}
     * @param e {@link Exception} causa
     * @throws IOException e
     */
    protected void errorInternalServerError(HttpServletResponse response, Exception e) throws IOException {
        this.error(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage(), response);
    }

    /**
     * @return String o prefixo - nome do resvico rest
     */
    public abstract String getPreffix();

    /**
     * Extrai o identification do request, que eh o nome imediatamente posterior ao nome do servico no {@link HttpServletRequest}
     * 
     * @param request {@link HttpServletRequest}
     * @return String ou <code>null</code> se nenhum for informado
     */
    protected String extractIdentification(HttpServletRequest request) {
        String pathInfo = request.getPathInfo();
        String[] split = pathInfo.substring(1).split("[/]");
        if (split.length == 0) {
            throw new IllegalArgumentException("wrong mapping");
        } else {
            if (!split[0].equals(this.getPreffix())) {
                throw new IllegalArgumentException("wrong mapping. Expected: " + this.getPreffix() + " but was: " + split[1]);
            }
            if (split.length > 1) {
                return split[1];
            }
        }
        return null;
    }

}
